/*
   DEONotificationCenter.m created by enoyau on Fri 13-Jan-1995

   You may freely copy, distribute, and reuse the code in this example.
   NeXT disclaims any warranty of any kind, expressed or implied, as to its
   fitness for any particular use.
*/

#import "DEONotificationCenter.h"
#import "DEONotification.h"
#import "DEOServerMonitor.h"
#import <foundation/NSException.h>

#import <libc.h>

NSString *DEOUpdate = @"DEOUpdate";
NSString *DEOInsert = @"DEOInsert";
NSString *DEODelete = @"DEODelete";

@interface DEONotificationCenter(Communication) <DEOClient>
- (void)registerAgain:(NSNotification *)note;
@end

// Holder object to allow the server to retain the proxy to
// a DEONotificationCenter without retaining the center itself.
// See comment in initWithDatabaseChannel:entity:
@interface DEONotificationCenterHolder : NSObject <DEOClient>
{
    DEONotificationCenter *center;
}
- initWithNonRetainedObject:(DEONotificationCenter *)aCenter;
@end


@implementation DEONotificationCenter

static NSString *localHostname = nil;

+ initialize
{
    if(!localHostname) {
        char DEOHostname[1024];

        gethostname(DEOHostname, 1024);
        localHostname = [[NSString alloc] initWithCString:DEOHostname];
    }
    return self;
}

+ (NSString *)localHostname
{
    return localHostname;
}

- initWithDatabaseChannel:(EODatabaseChannel *)aDatabaseChannel 
                   entity:(EOEntity *)anEntity
{
    NSArray *objs;
    NSArray *keys;
    
    [super init];

    // localCenter is used for notification.
    localCenter = [[NSNotificationCenter alloc] init];
    entity = [anEntity retain];
    databaseChannel = [aDatabaseChannel retain];

    // Build the unique identifier for the DEONotificationCenter
    objs = [NSArray arrayWithObjects:
        localHostname,
        [NSString stringWithFormat:@"%d", getpid()],
        [anEntity name],
        [NSString stringWithFormat:@"%x", self],
        0];

    keys = [NSArray arrayWithObjects:
        @"hostname",
        @"pid",
        @"entity",
        @"self",
        0];

    localID = [[NSDictionary dictionaryWithObjects:objs forKeys:keys] retain];

    // Register self for notifications from monitor
    monitor = [[DEOServerMonitor serverMonitor] retain];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(registerAgain:)
                                     notificationName:DEOServerReinitialize
                                               object:monitor];

    // We need to pass the server a pointer to our object, but it cannot
    // retain it, or a cycle will be created.  Unfortunately, the server
    // MUST retain the object we pass it or the proxy on the server side
    // will be released.  The solution is to pass the server an "holder"
    // object that it can retain (thus retaining the proxy) without
    // retaining us (thus avoiding a cycle).  This holder must pass on
    // messages to our actual instance.
    container =
        [[DEONotificationCenterHolder alloc] initWithNonRetainedObject:self];

    [self registerAgain:nil];
    
    return self;
}

- (void)dealloc
{
    [localCenter release];
    [entity release];
    [databaseChannel release];
    [localID release];
    NS_DURING
        [[monitor server] unregisterClient:(id <DEOClient>)container];
    NS_HANDLER
        NSLog(@"Failed to send remote method unregisterClient: to server.\n");
    NS_ENDHANDLER
    [container release];
    [monitor release];
    [super dealloc];
    return;
}

- (void)addObserver:observer
           selector:(SEL)selector
   notificationName:(NSString *)notificationName
             object:object
{
    [localCenter addObserver:observer
                    selector:selector
            notificationName:notificationName
                      object:object];
}

- (void)removeObserver:observer
      notificationName:(NSString *)notificationName
                object:object;
{
    [localCenter removeObserver:observer
               notificationName:notificationName
                         object:object];
}

- (void)removeObserver:observer
{
    [localCenter removeObserver:observer];
}

- (void)postNotificationName:(NSString *)notificationName object:object
{
    [self postNotificationName:notificationName object:object userInfo:nil];
}

- (void)postNotificationName:(NSString *)name
                      object:object
                    userInfo:(NSDictionary *)userInfo;
{
    NSMutableDictionary *info = [NSMutableDictionary dictionary];
    NSDictionary *primaryKey =
        [entity primaryKeyForRow:[[[databaseChannel databaseContext] database] snapshotForObject:object]];

    // Encodes the enterprise object in a dictionary with the primary key.
    [info setObject:primaryKey forKey:@"primaryKey"];
    [info setObject:name       forKey:@"notificationName"];
    [info setObject:localID    forKey:@"sender"];

    NS_DURING
        [[monitor server] dispatchInformation:info
                                       forKey:[entity name]
                                     userInfo:userInfo];
    NS_HANDLER
            NSLog(@"Failed to send remote method dispatchInformation:forKey:userInfo: to server.\n");
    NS_ENDHANDLER
}

@end

@implementation DEONotificationCenter(Communication)

- (oneway)dispatchInformation:(NSDictionary *)info
                       forKey:(NSString *)key
                     userInfo:(NSDictionary *)userInfo
{
    // Decodes the enterprise object from information over the wire
    id database = [[databaseChannel databaseContext] database];
    id eoObject = [database objectForPrimaryKey:[info objectForKey:@"primaryKey"]
                                         entity:entity];

    if(!eoObject) {
        // Creates an EOFault if object does not exist yet
        [EOFault objectFaultWithPrimaryKey:[info objectForKey:@"primaryKey"]
                                    entity:entity
                           databaseChannel:databaseChannel
                                      zone:NSDefaultMallocZone()];
    }

    [localCenter postNotification:
        [DEONotification notificationWithName:[info objectForKey:@"notificationName"]
                                       object:eoObject
                                     userInfo:userInfo
                                      isLocal:[[info objectForKey:@"sender"] isEqual:localID]]];

    return;
}

// Is called at init time or when the server has died.
- (void)registerAgain:(NSNotification *)note
{
    NS_DURING
        // Register as entity user in the distributed server
        [[monitor server] registerClient:(id <DEOClient>)container forKey:[entity name]];
    NS_HANDLER
        NSLog(@"Failed to send remote method registerClient:forKey: to server.\n");
    NS_ENDHANDLER
}

@end

@implementation DEONotificationCenterHolder

- initWithNonRetainedObject:(DEONotificationCenter *)aCenter
{
    [super init];
    center = aCenter;
    return self;
}

- (oneway)dispatchInformation:(NSDictionary *)info
                       forKey:(NSString *)key
                     userInfo:(NSDictionary *)userInfo
{
    [center dispatchInformation:info
                         forKey:key
                       userInfo:userInfo];
}
@end
